#include <rpc/rpc.h>

#include <err.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>

#include "tftp.h"


/* DEFINITIONS ****************************************************************/

#define MAX_LINE_LEN        (512)
#define ERR_FWRITE          "could not write to file"

/* Command_type_t e command_t semplificano il passaggio di informazioni *
 * sull'operazione di trasferimento all'interno del modulo client       */
typedef enum command_type_t {
  C_GET,
  C_PUT
} command_type_t;

typedef struct command_t {
  command_type_t type;    /* tipo di operazione: C_GET or C_PUT */
  char* localfilename;    /* percorso del file locale           */
  req_t req;              /* percorso del file remoto e mode    */
} command_t;

/******************************************************************************/


/* PROTOTYPES *****************************************************************/

/* Ciclo client principale: input -> validazione -> processing */
void client_loop();

/* Parsing della richiesta: in caso di successo, la variabile puntata da  *
 * `command` contiene i parametri                                         */
bool_t parse_req(char* line, command_t* command);

/* Assunto che la richiesta PUT sia stata accettata, porta a termine il *
 * trasferimento                                                        */
void process_put(FILE* file);

/* Assunto che la richiesta GET sia stata accettata, porta a termine il *
 * trasferimento                                                        */
void process_get(FILE* file);

/******************************************************************************/


/* GLOBALS ********************************************************************/

char* server;
CLIENT* clnt;

/******************************************************************************/


/**
 *  MAIN
**/
int main(int argc, char* argv[]) {

  /* argument check e inizializzazioni */
  if ( (argc < 3)
       || ((argc >= 3)                      &&
              (strcmp(argv[2], "tcp") != 0) &&
              (strcmp(argv[2], "udp") != 0) ) ) {
    errx(EXIT_FAILURE, "\n\tusage: %s <host> (tcp | udp)", argv[0]);
  }
  server = argv[1];
  clnt = clnt_create(server, TFTP, TFTP_VERSION, argv[2]);
  if (clnt == NULL) {
    clnt_pcreateerror(server);
    exit(EXIT_FAILURE);
  }
  /* ... sembra che la funzione authsys_create_default() non esista ... */
  clnt->cl_auth = authunix_create_default();

  client_loop();
  clnt_destroy(clnt);
  exit(EXIT_SUCCESS);
} /* END OF: main() */


/* IMPLEMENTATION OF LOCAL FUNCTIONS ******************************************/

/**
 *  CLIENT LOOP
**/
void client_loop() {
  char line[MAX_LINE_LEN];

  puts("Hello. Commands:\n"
      "\t\"put <localfile> <remotefile>\" (<remotefile> will be OVERWRITTEN!)\n"
      "\t\"get <localfile> <remotefile>\" (<localfile> will be OVERWRITTEN!)\n"
      "Command ?");

  while (fgets(line, MAX_LINE_LEN, stdin) != NULL) {
    command_t cmd;

    if (parse_req(line, &cmd) == TRUE) {
      FILE* localfile;
      maybe_t* result;

      /* controllo locale della richiesta */
      localfile = fopen(cmd.localfilename, (cmd.type == C_PUT)? "rb":"wb");
      if (localfile == NULL) {
        fprintf(stderr, "Local error: could not open %s\n", cmd.localfilename);
        puts("Command ?");
        continue;
      }
      /* processamento della richiesta */
      if (cmd.type == C_PUT)  result = put_1(&(cmd.req), clnt);
      else                    result = get_1(&(cmd.req), clnt);

      if (result == NULL) {
        clnt_perror(clnt, server);
        errx(EXIT_FAILURE, "RPC error");
      }
      if (result->ok == FALSE) {
        fprintf(stderr, "Remote error: %s\n", result->maybe_t_u.err.errstr);
      }
      else {
        /* le routine process_put e process_get operano in modalita' OCTET */
        if (cmd.type == C_PUT)  process_put(localfile);
        else                    process_get(localfile);
      }
      puts("Operation complete.");
      xdr_free((xdrproc_t)xdr_maybe_t, (char*)result);
      fclose(localfile);
    }
    else {
      fprintf(stderr, "Local error: command not recognized\n");
    }
    puts("Command ?");
  }
} /* END OF: client_loop() */


/**
 *  PARSE REQUEST
**/
bool_t parse_req(char* line, command_t* command) {
  char* p;
  char* p1;

  /* operazione: "put" o "get" (ignorando maiuscole/minuscole) */
  p = strtok(line, " ");
  if ( (p == NULL) ||
       ((strcasecmp(p, "put") != 0) && (strcasecmp(p, "get") != 0)) ){
    return FALSE;
  }
  command->type = (strcasecmp(p, "put") == 0)? C_PUT:C_GET;

  /* estrazione filename locale */
  p = strtok(NULL, " ");
  if ((p == NULL) || (strlen(p) <= 0)) {
    return FALSE;
  }
  command->localfilename = p;

  /* estrazione filename remoto (e rimozione newline) */
  p = strtok(NULL, " ");
  if ((p == NULL) || (strlen(p) <= 0)) {
    return FALSE;
  }
  p1 = strrchr(p, '\n');
  if (p1 != NULL) {
    *p1 = '\0';
  }
  command->req.filename = p;
  /* questo client opera solo in modalita' OCTET */
  command->req.mode = OCTET;

  return TRUE;
} /* END OF: parse_request() */


/**
 *  PROCESS PUT
**/
void process_put(FILE* file) {
  char buff[MAX_BLOCK_SIZE];
  block_t block;
  int size;
  maybe_t* result;

  do {

    size = fread(buff, 1, MAX_BLOCK_SIZE, file);
    block.block_t_val = malloc(size);
    if (block.block_t_val == NULL) {
      exit(EXIT_FAILURE);
    }
    block.block_t_len = size;
    memcpy(block.block_t_val, buff, size);

    result = put_block_1(&block, clnt);
    if (result == NULL) {
      clnt_perror(clnt, server);
      errx(EXIT_FAILURE, "RPC error");
    }
    free(block.block_t_val);

  } while ((result->ok == TRUE) && (size == MAX_BLOCK_SIZE));

  if (result->ok == FALSE) {
    fprintf(stderr, "Remote error: %s\n", result->maybe_t_u.err.errstr);
    xdr_free((xdrproc_t)xdr_maybe_t, (char*)result);
  }
} /* END OF: process_put() */


/**
 *  PROCESS GET
**/
void process_get(FILE* file) {
  maybe_block_t* result;
  bool_t error;
  unsigned int size = MAX_BLOCK_SIZE;

  error = FALSE;
  do {
    result = get_block_1(NULL, clnt);
    if (result == NULL) {
      clnt_perror(clnt, server);
      errx(EXIT_FAILURE, "RPC error");
    }

    if (result->ok == TRUE) {
      size = fwrite(result->maybe_block_t_u.block.block_t_val,
                    1, result->maybe_block_t_u.block.block_t_len, file);

      if (size != result->maybe_block_t_u.block.block_t_len) {
        /* errore di scrittura su file: reporting locale e remoto */
        err_t err;
        char* err_result;
        err.errcode = ACCESS_VIOLATION;
        err.errstr  = ERR_FWRITE;
        err_result = error_1(&err, clnt);
        fprintf(stderr, "Local error: %s\n", ERR_FWRITE);

        /* error-check dell'error report remoto */
        if (result == NULL) {
          clnt_perror(clnt, server);
          errx(EXIT_FAILURE, "RPC error");
        }
        error = TRUE;
      }
    }
    else {
      fprintf(stderr, "Remote error: %s\n", result->maybe_block_t_u.err.errstr);
      error = TRUE;
    }
    xdr_free((xdrproc_t)xdr_maybe_block_t, (char*)result);

  } while ((error == FALSE) && (size == MAX_BLOCK_SIZE));
} /* END OF: process_get() */

